package helper.ldap;

import facade.AppContext;
import helper.GlobalConfigKeys;
import org.apache.commons.lang.StringUtils;
import play.Logger;
import util.common.CollectionHelper;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;

public class LdapHelper {

    /**
     * LDAP登录方法.
     */
    public static boolean login(String username, String password) {
        // 判断必填字段是否全部填写
        if (StringUtils.isEmpty(username)
                || StringUtils.isEmpty(AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_DOMAIN))
                || StringUtils.isEmpty(AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_HOST))) {
            Logger.info("LOG60371: 必填信息为空，LDAP连接失败！");
            return false;
        }
        String account = username + "@" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_DOMAIN);

        Properties env = getLdapProperties(account, password);
        try {
            InitialLdapContext dc = new InitialLdapContext(env, null);
            Logger.info("LOG60372: 域用户" + username + " 登录" + account + "成功！");
            return true;
        } catch (Exception e) {
            //Logger.warn(e, "TLQ00260: login failed");
            Logger.info("LOG60373: 域用户" + username + " 登录" + account + "失败！" + e.getMessage());
            //Scope.Flash.current().put("warn", "登录失败:" + e.getMessage());
            return false;
        }
    }

    /**
     * 执行Ldap方法.
     */
    public static void doInLdapContext(LdapContextCallback callback) {
        Properties env = new Properties();
        String ldapURL = "LDAP://" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_HOST) + ":" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_PORT);
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        //AD域的账户记得添加@+域名
        String account = AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_USERNAME) + "@" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_DOMAIN);

        env.put(Context.SECURITY_PRINCIPAL, account);
        env.put(Context.SECURITY_CREDENTIALS, AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_PASSWORD)); //密码 使用统一的认证用的
        env.put(Context.PROVIDER_URL, ldapURL);
        LdapContext ctx = null;
        try {
            ctx = new InitialLdapContext(env, null);
            callback.apply(ctx);
        } catch (Exception e) {
            Logger.warn(e, "TLQ00550: LDAP出错");
            //throw new RuntimeException("LDAP出错:" + e.getMessage());
        } finally {
            closeLdap(ctx);
        }

    }

    /**
     * 得到属性的值.
     */
    public static String getAttributeValue(Attributes attributes, String name) throws NamingException {
        Attribute attribute = attributes.get(name);
        if (attribute == null) {
            return null;
        }
        Enumeration vals = attribute.getAll();
        if (vals.hasMoreElements()) {
            Object o = vals.nextElement();
            return String.valueOf(o);
        }
        return null;
    }

    /**
     * 从以逗号分隔的名字中，按顺序取值.
     */
    public static String getAttributeValueByNames(Attributes attributes, String names) throws NamingException {
        if (StringUtils.isBlank(names)) {
            return null;
        }

        List<String> nameList = CollectionHelper.convertStringToListUseSplit(names, ",");

        for (String name : nameList) {
            if (StringUtils.isNotBlank(name)) {
                String value = getAttributeValue(attributes, name.trim());
                if (StringUtils.isNotBlank(value)) {
                    return value;
                }
            }
        }
        return null;
    }

    public static List<String> getAttributeValues(Attributes attributes, String name) throws NamingException {
        Attribute attribute = attributes.get(name);
        if (attribute == null) {
            return null;
        }
        Enumeration vals = attribute.getAll();
        List<String> result = new ArrayList<>();
        while (vals.hasMoreElements()) {
            Object o = vals.nextElement();
            result.add(String.valueOf(o));
        }
        return result;
    }

    public static String getBaseOuName(String baseName) {
        return baseName.substring(baseName.indexOf(",") + 1);
    }

    // ---------- 以下是测试用的, 上面是正式的helper方法. ----------
    public static void getInfo(String ldapPath, String objectClass) throws NamingException {
        doInLdapContext(ctx -> {
            SearchControls searchCtls = new SearchControls();
            searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            Logger.info("TLQ00590: GlobalConfig.LDAP_BASE_NAME = " + ldapPath);
            // 注意OU和DC的先后顺序
            NamingEnumeration results = ctx.search(ldapPath,
                    "objectClass=" + objectClass, searchCtls);
            while (results.hasMoreElements()) {
                SearchResult sr = (SearchResult) results.next();
                Logger.info("TLQ00300: -------------------- result.name:" + sr.getName() + " class:" + sr.getClassName() + " d:" + sr.getNameInNamespace());

                Attributes attributes = sr.getAttributes();
                Logger.info("TLQ00580: name=" + getAttributeValue(attributes, "name"));
                String distinguishedName = getAttributeValue(attributes, "distinguishedName");
                Logger.info("TLQ00580: distinguishedName=" + distinguishedName);
                Logger.info("TLQ00580: distinguishedName baseOu=" + getBaseOuName(distinguishedName));

                Logger.info("TLQ00560:  ----------------------- for each -----------");
                NamingEnumeration values = attributes.getAll();
                while (values.hasMore()) {
                    Attribute attr = (Attribute) values.next();
                    Enumeration vals = attr.getAll();
                    while (vals.hasMoreElements()) {
                        Object o = vals.nextElement();
                        Logger.info("    ..........." + attr.getID() + ", objclass=" + o.getClass().getName() + ": --------------"
                                + o.toString());
                    }
                }

            }
        });
    }

    private static Properties getLdapProperties(String account, String password) {
        Properties env = new Properties();
        String ldapURL = "LDAP://" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_HOST) + ":" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_PORT);
        env.put(Context.PROVIDER_URL, ldapURL);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, account);
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        //批量处理
        env.put(Context.BATCHSIZE, "50");
        // 连接超时设置
        env.put("com.sun.jndi.ldap.connect.timeout", "3000");
        // LDAP连接池
        env.put("com.sun.jndi.ldap.connect.pool", "true");
        // LDAP连接池最大数
        env.put("com.sun.jndi.ldap.connect.pool.maxsize", "3");
        // LDAP连接池优先数
        env.put("com.sun.jndi.ldap.connect.pool.prefsize", "1");
        // LDAP连接池超时
        env.put("com.sun.jndi.ldap.connect.pool.timeout", "300000");
        // LDAP连接池初始化数
        env.put("com.sun.jndi.ldap.connect.pool.initsize", "1");
        // LDAP连接池的认证方式
        env.put("com.sun.jndi.ldap.connect.pool.authentication", "simple");
        return env;
    }

    /**
     * 初始化ldap
     */
    public static InitialLdapContext initLdap() {
        //ad服务器
        String url = "ldap://" + AppContext.getGlobalConfig(GlobalConfigKeys.LDAP_HOST);//默认端口为80的可以不用填写，其他端口需要填写，如ldap://xxx.com:8080
        String adminName = "admin@xxx.com";// 注意用户名的写法：domain\User 或  User@domain.com
        String adminPassword = "admin";
        Hashtable<String, String> HashEnv = new Hashtable<>();
        HashEnv.put(Context.SECURITY_AUTHENTICATION, "simple"); // LDAP访问安全级别
        HashEnv.put(Context.SECURITY_PRINCIPAL, adminName); // AD User
        HashEnv.put(Context.SECURITY_CREDENTIALS, adminPassword); // AD Password
        HashEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // LDAP工厂类
        HashEnv.put(Context.PROVIDER_URL, url);
        try {
            InitialLdapContext ctx = new InitialLdapContext(HashEnv, null);
            System.out.println("初始化ldap成功！");
            return ctx;
        } catch (NamingException e) {
            e.printStackTrace();
            System.err.println("Throw Exception : " + e);
        }
        return null;
    }

    /**
     * 关闭ldap
     */
    public static void closeLdap(LdapContext ctx) {
        try {
            if (ctx != null) {
                ctx.close();
            }
        } catch (NamingException e) {
            Logger.warn(e, "TLQ00540: 关闭时出现异常");
        }
    }

    /**
     * @param type organizationalUnit:组织架构 group：用户组 user|person：用户
     * @param name
     * @return
     */
    public static String getAdInfo(String type, String filter, String name) {

        String userName = name; // 用户名称
        if (userName == null) {
            userName = "";
        }
        String company = "";
        String result = "";

        InitialLdapContext ctx = initLdap();

        if (ctx == null) {
            Logger.info("TLQ00270: 初始化失败");
            return null;
        }

        try {
            // 域节点
            String searchBase = "DC=xx,DC=xxx,DC=com";
            // LDAP搜索过滤器类
            //cn=*name*模糊查询 cn=name 精确查询
//          String searchFilter = "(objectClass="+type+")";
            String searchFilter = "(&(objectClass=" + type + ")(" + filter + "=*" + name + "*))";
            // 创建搜索控制器
            SearchControls searchCtls = new SearchControls();
            //  设置搜索范围
            searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//          String returnedAtts[] = {  "memberOf" }; // 定制返回属性
//      searchCtls.setReturningAttributes(returnedAtts); // 设置返回属性集 不设置则返回所有属性
            // 根据设置的域节点、过滤器类和搜索控制器搜索LDAP得到结果
            NamingEnumeration answer = ctx.search(searchBase, searchFilter, searchCtls);// Search for objects using the filter
            // 初始化搜索结果数为0
            int totalResults = 0;// Specify the attributes to return
            int rows = 0;
            while (answer.hasMoreElements()) {// 遍历结果集
                SearchResult sr = (SearchResult) answer.next();// 得到符合搜索条件的DN
                ++rows;
                String dn = sr.getName();
                System.out.println(dn);
                Attributes Attrs = sr.getAttributes();// 得到符合条件的属性集
                if (Attrs != null) {
                    try {
                        for (NamingEnumeration ne = Attrs.getAll(); ne.hasMore(); ) {
                            Attribute Attr = (Attribute) ne.next();// 得到下一个属性
                            System.out.println(" AttributeID=属性名：" + Attr.getID().toString());
                            // 读取属性值
                            for (NamingEnumeration e = Attr.getAll(); e.hasMore(); totalResults++) {
                                company = e.next().toString();
                                System.out.println("    AttributeValues=属性值：" + company);
                            }
                            System.out.println("    ---------------");

                        }
                    } catch (NamingException e) {
                        System.err.println("Throw Exception : " + e);
                    }
                }// if
            }// while
            System.out.println("************************************************");
            System.out.println("Number: " + totalResults);
            System.out.println("总共用户数：" + rows);
        } catch (NamingException e) {
            Logger.warn(e, "LOG07223: Error");
        } finally {
            if (ctx != null) {
                try {
                    ctx.close();
                } catch (NamingException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    public static void main(String args[]) {
        // 实例化

        getAdInfo("user", "cn", "李XX");//查找用户
        getAdInfo("organizationalUnit", "ou", "工程");//查找组织架构
        getAdInfo("group", "cn", "福建xxx");//查找用户组

    }
}  
